React Lifting Up State
#
Learning Objectives- Learn how to lift state up from a component
React's data flow is unidirectional and data flows down: Data moves from parent components down. A parent component has no idea about the state of it's child components.
As we were updating our state of our individual products, the state in the parent component did not change.
Let's say we would want to make a list of favorite songs, that when a user clicks on an item, it gets a heart but also goes into the favorite songs list. We would need to move that information out of the SongList
and into a FavoriteSongs
component. There is no direct way to move this data from the SongList
to the FavoriteSongs
component - we have to move the data up into the shared parent component of SongList
and FavoriteSongs
and then dispatch the data to the FavoriteSongs
.
If this seems, a bit unwieldy, especially if you have a lot of state to manage, a lot of people would agree. Therefore there are solutions to help, for example, there are other libraries like Redux
that help manage state.
For now, we'll learn the basics of how to lift up state.
We also want to update our functionality of our onClick inside of the SongList
, rather than toggle a true/false value, we want to take that item and add it to the App
's state and then pass it down to the favorite songs.
Let's make a FavoriteSongs
component. It's going to be very similar to our SongList
, as a bonus, you could think about whether there would be a way to reuse our SongList or if it makes sense to keep them separate.
class FavoriteSongs extends Component { render() { return ( <table> <thead> <tr> <th>Song</th> <th>Artist</th> <th>Time</th> </tr> </thead> <tbody> {this.props.songs.map((song, index) => { return ( <tr key={index}> <td>{song.title}</td> <td>{song.artist}</td> <td>{song.time}</td> </tr> ); })} </tbody> </table> ); }}
Now Let's import this new component: App.js
import React, { Component } from "react";import playlists from "./data.js";import SongList from "./components/SongList.js";import FavoriteSongs from "./components/FavoriteSongs.js";
Add an array to the state of the App component called lovedSongs
#
App.jsthis.state = { playlists: playlists, title: "", artist: "", time: "0:00", lovedSongs: [],};
Let's make a function that will add to our lovedSongs in our App
#
App.jsaddLovedSong(song) { this.setState({ lovedSongs: [song, ...this.state.lovedSongs] });}
and don't forget to bind this
in the constructor
this.addLovedSong = this.addLovedSong.bind(this);
We have to pass this function down to our SongList
<SongList playlists={this.state.playlists} handleAdd={this.addLovedSong} />
Let's render this component(it'll be empty at first):
#
App.js<div className="playlists"> <SongList playlists={this.state.playlists} handleAdd={this.addLovedSong} /> <FavoriteSongs songs={this.state.lovedSongs} /></div>
Now we have to pass addLovedSong
even further down into our Song component (this is called props drilling and is something that redux looks to solve)
#
SongList<Song song={song} key={index} handleAdd={this.props.handleAdd} />
Finally, we can call this function inside of our click event. However, we have two problems
- we need to be able to pass an argument into the function. We can't do it onClick or else it will fire immediately
- we already have another function being triggered on click
To solve this, we can wrap both functions in an anonymous function
#
Song<tr key={this.props.index} onClick={() => { this.props.handleAdd(this.props.song); this.toggleLove(); }}>...</tr>;
Finally, let's add the songs in our list so that they render in our favorite songs component
#
Extra bonuses- Whenever we click we add a song Fix it so that only loved songs can be added
- We can add the same song multiple times - make it so that you can only add a song once
- Make is so that you can remove a song from the favorites list